using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using Seems.Framework.Extensions;
using Seems.Model;
using Seems.SL.VM;

namespace Seems.View.Controls
{
	/// <summary>
	/// Special kind of a panel that occupies bounding rectangle of child controls. 
	/// Assumes children are displaced using render transform.
	/// </summary>
	public class Contour : Panel
	{
		private ScrollViewer scroller;

		protected override Size MeasureOverride(Size availableSize)
		{
			// designtime checks
			if (Scroller == null || DataContext == null)
				return new Size(100,100);

			// ignore availableSize because scroller provide us with Infinity,Infinity all the time
			availableSize.Width = Scroller.ScrollableWidth;
			availableSize.Height = Scroller.ScrollableHeight;
			Size result = availableSize;
			var inf = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
			double minX = 0;
			double maxX = Scroller.ViewportWidth - 1;
			double minY = 0;
			double maxY = Scroller.ViewportHeight - 1;

			foreach (var child in Children)
			{
				child.Measure(inf);
				var x = ((TranslateTransform)child.RenderTransform).X;
				var y = ((TranslateTransform)child.RenderTransform).Y;
				minX = Math.Min(x, minX);
				minY = Math.Min(y, minY);
				maxX = Math.Max(x + child.DesiredSize.Width, maxX);
				maxY = Math.Max(y + child.DesiredSize.Height, maxY);
			}
			if (Children.Count > 0)
			{
				result.Width = maxX - minX;
				result.Height = maxY - minY;
			}

			if (!double.IsInfinity(availableSize.Width) && result.Width < availableSize.Width)
				result.Width = availableSize.Width;
			if (!double.IsInfinity(availableSize.Height) && result.Height < availableSize.Height)
				result.Height = availableSize.Height;

			
			var scaleProvider = DataContext as IScaleProvider;
			if (scaleProvider != null)
			{
				scaleProvider.ContourSize = result;
				var scale = scaleProvider.Scale;
				result.Width *= scale;
				result.Height *= scale;
			}

			return result;

		}

		protected override Size ArrangeOverride(Size finalSize)
		{
			foreach (var child in Children)
			{
				child.Arrange(new Rect(new Point(0, 0), child.DesiredSize));
			}
			return finalSize;
		}

		public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
			"ItemsSource", typeof(IEnumerable<ElementShownOnDiagramVM>), typeof(Contour), new PropertyMetadata(OnItemSourceChanged));


		public IEnumerable<ElementShownOnDiagramVM> ItemsSource
		{
			get
			{
				return (IEnumerable<ElementShownOnDiagramVM>) GetValue(ItemsSourceProperty);
			}
			set
			{
				SetValue(ItemsSourceProperty, value);
			}
		}

		public ScrollViewer Scroller
		{
			get { return scroller ?? (scroller = this.Ancestor<ScrollViewer>()); }
		}

		private static void OnItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var panel = d as Contour;
			if (panel != null)
			{
				var oldData = e.OldValue as INotifyCollectionChanged;
				var newData = e.NewValue as INotifyCollectionChanged;
				panel.ItemSourceChanged(oldData, newData);
			}
		}

		private void ItemSourceChanged(INotifyCollectionChanged oldData, INotifyCollectionChanged newData)
		{
			if (oldData != null)
				oldData.CollectionChanged -= AddOrRemoveNode;

			newData.CollectionChanged += AddOrRemoveNode;

			Children.Clear();
			foreach (var o in ItemsSource)
				AddNode(o);
		}

		private void AddOrRemoveNode(object sender, NotifyCollectionChangedEventArgs e)
		{
			switch (e.Action)
			{
				case NotifyCollectionChangedAction.Add:
					foreach (var o in e.NewItems)
						AddNode((ElementShownOnDiagramVM) o);
					break;

				case NotifyCollectionChangedAction.Remove:
					foreach (object o in e.OldItems)
					{
						IEnumerable<FrameworkElement> removals = ModelToView((DiagramElement) o);

						foreach (FrameworkElement remove in removals)
						{
							//if (remove == SelectedEntity) SelectedEntity = null;
							//if (remove == _adorner) _adorner = null;
							Children.Remove(remove);
						}
					}
					break;
				case NotifyCollectionChangedAction.Replace:
				case NotifyCollectionChangedAction.Reset:
					break;
			}
		}

		private IEnumerable<FrameworkElement> ModelToView(DiagramElement model)
		{
			return this.Descendants<FrameworkElement>().Where(i => i.DataContext == model);
		}

		/// <summary>
		/// Adds a node to the canvas, loading it's template from the xaml.
		/// </summary>
		/// <param name="vm"></param>
		private void AddNode(ElementShownOnDiagramVM vm)
		{
			var view = CreateView(vm);
			if (view != null)
			{
				var translateTransform = new TranslateTransform();
				view.RenderTransform = translateTransform;

				BindingOperations.SetBinding(translateTransform, TranslateTransform.XProperty, new Binding("Position.X") { Source = vm });
				BindingOperations.SetBinding(translateTransform, TranslateTransform.YProperty, new Binding("Position.Y") { Source = vm });
				view.DataContext = vm;
				Children.Add(view);
			}
		}

		private FrameworkElement CreateView(ElementShownOnDiagramVM vm)
		{
			//if (vm.Model != null && vm.Model.Element != null)
			return new ElementView();
			//return null;
		}
	}

	public interface IScaleProvider
	{
		Size ContourSize { set; }
		double Scale { get; }
	}
}